package io.github.wangtisnruipopo.carscore.types;

import cn.hutool.core.util.ClassUtil;
import io.github.wangtisnruipopo.carscore.enums.CardValueEnum;
import io.github.wangtisnruipopo.carscore.enums.DecorEnum;
import lombok.Getter;
import lombok.SneakyThrows;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public abstract class AbsSuitCards<D extends AbsDecorValue, V extends AbsCardValue, C extends AbsSimpleCard<D, V>> implements ISuitCards<D, V, C> {

    @Getter
    protected List<C> cards;
    protected List<C> playedCards;

    protected Class<D> decorType;
    protected Class<V> cardValueType;
    protected Class<C> cardType;

    {
        this.create();
    }

    @Override
    @SuppressWarnings("unchecked")
    public void create() {
        this.decorType = (Class<D>) ClassUtil.getTypeArgument(this.getClass());
        this.cardValueType = (Class<V>) ClassUtil.getTypeArgument(this.getClass(), 1);
        this.cardType = (Class<C>) ClassUtil.getTypeArgument(this.getClass(), 2);
        this.cards = new ArrayList<>();
        this.playedCards = new ArrayList<>();
        int total = this.setsNumber();
        DecorEnum[] decorEnums = DecorEnum.values();
        CardValueEnum[] cardValueEnums = CardValueEnum.values();
        while (total-- > 0) {
            for (DecorEnum decor : decorEnums) {
                for (CardValueEnum cardValue : cardValueEnums) {
                    if (cardValue == CardValueEnum.VALUE_SMALL_KING || cardValue == CardValueEnum.VALUE_BIG_KING) {
                        continue;
                    }
                    C card = this.createCard(decor, cardValue);
                    this.cards.add(card);
                }
            }
        }
        // 添加大小王
        C skCard = this.createCard(null, CardValueEnum.VALUE_SMALL_KING);
        C bkCard = this.createCard(null, CardValueEnum.VALUE_BIG_KING);
        this.cards.add(skCard);
        this.cards.add(bkCard);
        this.cards = this.cards.stream().filter(this.filter()).collect(Collectors.toList());
        this.cards.sort(Comparable::compareTo);
        this.afterCreate(this.cards);
    }

    @SneakyThrows
    public C createCard(DecorEnum decor, CardValueEnum cardValue) {
        D d = decorType.getDeclaredConstructor().newInstance();
        d.setValue(decor);
        V v = cardValueType.getDeclaredConstructor().newInstance();
        v.setValue(cardValue);
        C card = cardType.getDeclaredConstructor().newInstance();
        card.setDecor(d);
        card.setValue(v);
        return card;
    }

    @Override
    public Predicate<C> filter() {
        return this::check;
    }

    @Override
    public C drawFromTop() {
        return this.drawFromIndex(0);
    }

    @Override
    public C drawFromBottom() {
        return this.drawFromIndex(this.cards.size() - 1);
    }

    @Override
    public C drawFromIndex(int index) {
        if (this.cards.isEmpty()) {
            this.arrangeCards();
        }
        if (index < 0 || index >= this.cards.size()) return null;
        C card = this.cards.get(index);
        this.cards = this.cards.stream().filter(c -> c != card).collect(Collectors.toList());
        return card;
    }

    @Override
    public C drawRandom() {
        int index = (int) (Math.random() * this.cards.size());
        return this.drawFromIndex(index);
    }

    @Override
    public void shuffle() {
        Collections.shuffle(this.cards);
    }

    @Override
    public boolean hasCards() {
        return this.cards != null && this.cards.size() > 0;
    }

    @Override
    public void pushPlayedCard(C card) {
        this.playedCards.add(card);
    }

    @Override
    public void arrangeCards() {
        this.cards.addAll(this.playedCards);
        this.playedCards = new ArrayList<>();
    }

    public abstract void afterCreate(List<C> cards);

    public abstract boolean check(C card);
}